package com.javalaoniu.tool.http.manage;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author: xianninig
 * @date: 2018/8/29 16:33
 */
public class LocalHttpConnectionManager {
    private static final Logger LOG = LoggerFactory.getLogger(LocalHttpConnectionManager.class);
    private static PoolingHttpClientConnectionManager manager = null;


    static {
        LayeredConnectionSocketFactory socketFactory = null;

        try {
            socketFactory = new SSLConnectionSocketFactory(SSLContext.getDefault());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", socketFactory)
                .register("http", new PlainConnectionSocketFactory())
                .build();
        manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        //设置最大连接数
        manager.setMaxTotal(200);
        //将每个路由基础的连接增加
        manager.setDefaultMaxPerRoute(20);
        //validateAfterInactivity 空闲永久连接检查间隔，这个牵扯的还比较多
        //官方推荐使用这个来检查永久链接的可用性，而不推荐每次请求的时候才去检查
        manager.setValidateAfterInactivity(60000);

        new IdleConnectionMonitorTask(manager).lanuchTimer();
    }

    private static HttpRequestRetryHandler getHttpRequestRetryHandler() {

        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {

            @Override
            public boolean retryRequest(IOException exception,
                                        int executionCount, HttpContext context) {
                /**
                 * 设置重试的次数
                 */
                if (executionCount >= 5) {
                    return false;
                }
                /**
                 * 如果服务器丢掉了连接，那么就重试
                 */
                if (exception instanceof NoHttpResponseException) {
                    return true;
                }
                /**
                 * 不要重试SSL握手异常
                 */
                if (exception instanceof SSLHandshakeException) {
                    return false;
                }
                /**
                 * 超时
                 */
                if (exception instanceof InterruptedIOException) {
                    return false;
                }
                /**
                 * 目标服务器不可达
                 */
                if (exception instanceof UnknownHostException) {
                    return false;
                }
                /**
                 * 连接被拒绝
                 */
                if (exception instanceof ConnectTimeoutException) {
                    return false;
                }
                /**
                 * SSL握手异常
                 */
                if (exception instanceof SSLException) {
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext
                        .adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        };

        return httpRequestRetryHandler;
    }

    public static CloseableHttpClient getHttpClient() {
        try {
            //创建SSLContext
            SSLContext sslContext = SSLContext.getInstance("SSL");
            TrustManager[] tm = new TrustManager[]{new MyX509TrustManager()};
            //初始化
            sslContext.init(null, tm, new java.security.SecureRandom());
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
            //获取httpClient
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
                    .setConnectionManager(manager).setRetryHandler(getHttpRequestRetryHandler())
                    .build();

            /*CloseableHttpClient httpClient = HttpClients.createDefault();//如果不采用连接池就是这种方式获取连接*/
            return httpClient;
        } catch (Exception e) {

        }

        return null;
    }

    /**
     * 监控有异常的链接
     */
    private static class IdleConnectionMonitorTask{
        private final HttpClientConnectionManager connectionManager;
        public ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(1);

        public IdleConnectionMonitorTask(HttpClientConnectionManager connMgr) {
            super();
            this.connectionManager = connMgr;
        }

        /**
         * 定期清理空闲链接的任务
         */
        public void lanuchTimer() {
            final Runnable task = () -> {
                connectionManager.closeExpiredConnections();
                connectionManager.closeIdleConnections(3, TimeUnit.SECONDS);
            };
            scheduExec.scheduleWithFixedDelay(task, 1000 * 5, 1000 * 3, TimeUnit.MILLISECONDS);
        }

    }

}
